package org.xwalk.core;

import android.app.Activity;
import android.content.Context;
import android.content.Intent;

import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.ArrayList;

/**
 * <p>XWalkExternalExtensionManager represents an external extension manager.
 * Its instance would be created While constructing XWalkView.
 * XWalkView embedders could get the manager by calling XWalkView.getExtensionManager(),
 * and then employ the manager to load their own external extensions by path,
 * although XWalkView embedders must package their external extensions into the apk beforehand.</p>
 *
 * <pre>What would XWalkView embedders do:
 * 1, package extension MyExtension.js/json files into apk folder assets/xwalk-extensions/MyExtension.
 * 2, package MyExtension.jar into apk.
 * 3, add permissions required by MyExtension into AndroidManifest.xml.
 * 4, call XWalkView.getExtensionManager().loadExtension("xwalk-extensions/MyExtension") to load.
 * </pre>
 *
 * <p>In embedded mode, developers can call XWalkView.getExtensionManager() to load extensions
 * directly after the creation of XWalkView. But in shared mode and lite mode, the Crosswalk runtime
 * isn't loaded yet at the moment the activity is created, so can't load extensions immediately.
 * To make your code compatible with all modes, please refer to the examples in {@link XWalkActivity} or
 * {@link XWalkInitializer} to make sure to load extensions after XWalk runtime ready.</p>
 */

public abstract class XWalkExternalExtensionManager   {

    private ArrayList<Object> constructorTypes;
    private ArrayList<Object> constructorParams;
    private ReflectMethod postWrapperMethod;


    private XWalkCoreWrapper coreWrapper;
    private Object bridge;

    protected Object getBridge() {
        return bridge;
    }


    /**
     * Constructs a new XWalkExternalExtensionManager for the XWalkView.
     * @param view the current XWalkView object.
     */
    public XWalkExternalExtensionManager(XWalkView view) {
        constructorTypes = new ArrayList<Object>();
        constructorTypes.add("XWalkViewBridge");

        constructorParams = new ArrayList<Object>();
        constructorParams.add(view);

        reflectionInit();
    }

    /**
     * Get current Activity for XWalkView.
     * @return the current Activity.
     * @deprecated This method is no longer supported
     */
    @Deprecated
    public Activity getViewActivity() {
        try {
            return (Activity)getViewActivityMethod.invoke();
        } catch (UnsupportedOperationException e) {
            if (coreWrapper == null) {
                throw new RuntimeException("Crosswalk's APIs are not ready yet");
            } else {
                XWalkCoreWrapper.handleRuntimeError(e);
            }
        }
        return null;
    }

    private ReflectMethod getViewActivityMethod = new ReflectMethod(null, "getViewActivity");

    /**
     * Get current Context for XWalkView.
     * @return the current Context.
     */
    public Context getViewContext() {
        try {
            return (Context)getViewContextMethod.invoke();
        } catch (UnsupportedOperationException e) {
            if (coreWrapper == null) {
                throw new RuntimeException("Crosswalk's APIs are not ready yet");
            } else {
                XWalkCoreWrapper.handleRuntimeError(e);
            }
        }
        return null;
    }

    private ReflectMethod getViewContextMethod = new ReflectMethod(null, "getViewContext");

    /**
     * Load one single external extension by its path.
     * Do nothing here, just expose to be implemented by XWalkExternalExtensionManagerImpl.
     * @param extensionPath the extension folder containing extension js/json files.
     */
    public void loadExtension(String extensionPath) {
        try {
            loadExtensionStringMethod.invoke(extensionPath);
        } catch (UnsupportedOperationException e) {
            if (coreWrapper == null) {
                throw new RuntimeException("Crosswalk's APIs are not ready yet");
            } else {
                XWalkCoreWrapper.handleRuntimeError(e);
            }
        }
        
    }

    private ReflectMethod loadExtensionStringMethod = new ReflectMethod(null, "loadExtension");

    /**
     * Notify onStart().
     * Extension manager should propagate to all external extensions.
     */
    public void onStart() {
        try {
            onStartMethod.invoke();
        } catch (UnsupportedOperationException e) {
            if (coreWrapper == null) {
                throw new RuntimeException("Crosswalk's APIs are not ready yet");
            } else {
                XWalkCoreWrapper.handleRuntimeError(e);
            }
        }
        
    }

    private ReflectMethod onStartMethod = new ReflectMethod(null, "onStart");

    /**
     * Notify onResume().
     * Extension manager should propagate to all external extensions.
     */
    public void onResume() {
        try {
            onResumeMethod.invoke();
        } catch (UnsupportedOperationException e) {
            if (coreWrapper == null) {
                throw new RuntimeException("Crosswalk's APIs are not ready yet");
            } else {
                XWalkCoreWrapper.handleRuntimeError(e);
            }
        }
        
    }

    private ReflectMethod onResumeMethod = new ReflectMethod(null, "onResume");

    /**
     * Notify onPause().
     * Extension manager should propagate to all external extensions.
     */
    public void onPause() {
        try {
            onPauseMethod.invoke();
        } catch (UnsupportedOperationException e) {
            if (coreWrapper == null) {
                throw new RuntimeException("Crosswalk's APIs are not ready yet");
            } else {
                XWalkCoreWrapper.handleRuntimeError(e);
            }
        }
        
    }

    private ReflectMethod onPauseMethod = new ReflectMethod(null, "onPause");

    /**
     * Notify onStop().
     * Extension manager should propagate to all external extensions.
     */
    public void onStop() {
        try {
            onStopMethod.invoke();
        } catch (UnsupportedOperationException e) {
            if (coreWrapper == null) {
                throw new RuntimeException("Crosswalk's APIs are not ready yet");
            } else {
                XWalkCoreWrapper.handleRuntimeError(e);
            }
        }
        
    }

    private ReflectMethod onStopMethod = new ReflectMethod(null, "onStop");

    /**
     * Notify onDestroy().
     * <strong>Pleaes invoke super.onDestroy() first when you overriding this method.</strong>
     * Extension manager should propagate to all external extensions.
     */
    public void onDestroy() {
        try {
            onDestroyMethod.invoke();
        } catch (UnsupportedOperationException e) {
            if (coreWrapper == null) {
                throw new RuntimeException("Crosswalk's APIs are not ready yet");
            } else {
                XWalkCoreWrapper.handleRuntimeError(e);
            }
        }
        
    }

    private ReflectMethod onDestroyMethod = new ReflectMethod(null, "onDestroy");

    /**
     * Notify onActivityResult().
     * Extension manager should propagate to all external extensions.
     * @param requestCode the request code.
     * @param resultCode the result code.
     * @param data the Intent data received.
     * @deprecated This method is no longer supported
     */
    @Deprecated
    public void onActivityResult(int requestCode, int resultCode, Intent data) {
        try {
            onActivityResultintintIntentMethod.invoke(requestCode, resultCode, data);
        } catch (UnsupportedOperationException e) {
            if (coreWrapper == null) {
                throw new RuntimeException("Crosswalk's APIs are not ready yet");
            } else {
                XWalkCoreWrapper.handleRuntimeError(e);
            }
        }
        
    }

    private ReflectMethod onActivityResultintintIntentMethod = new ReflectMethod(null, "onActivityResult");

    /**
     * Notify onNewIntent().
     * Extension manager should propagate to all external extensions.
     * @param intent the Intent received.
     */
    public abstract void onNewIntent(Intent intent);


    void reflectionInit() {
        XWalkCoreWrapper.initEmbeddedMode();

        coreWrapper = XWalkCoreWrapper.getInstance();
        if (coreWrapper == null) {
            XWalkCoreWrapper.reserveReflectObject(this);
            return;
        }

        int length = constructorTypes.size();
        Class<?>[] paramTypes = new Class<?>[length+1];
        for (int i = 0; i < length; ++i) {
            Object type = constructorTypes.get(i);
            if (type instanceof String) {
                paramTypes[i] = coreWrapper.getBridgeClass((String) type);
                constructorParams.set(i, coreWrapper.getBridgeObject(constructorParams.get(i)));
            } else if (type instanceof Class<?>) {
                paramTypes[i] = (Class<?>) type;
            } else {
                assert(false);
            }
        }

        paramTypes[length] = Object.class;
        constructorParams.add(this);

        ReflectConstructor constructor = new ReflectConstructor(
                coreWrapper.getBridgeClass("XWalkExternalExtensionManagerBridge"), paramTypes);
        try {
            bridge = constructor.newInstance(constructorParams.toArray());
        } catch (UnsupportedOperationException e) {
            return;
        }

        if (postWrapperMethod != null) postWrapperMethod.invoke();

        getViewActivityMethod.init(bridge, null,
                "getViewActivitySuper");
        getViewContextMethod.init(bridge, null,
                "getViewContextSuper");
        loadExtensionStringMethod.init(bridge, null,
                "loadExtensionSuper", String.class);
        onStartMethod.init(bridge, null,
                "onStartSuper");
        onResumeMethod.init(bridge, null,
                "onResumeSuper");
        onPauseMethod.init(bridge, null,
                "onPauseSuper");
        onStopMethod.init(bridge, null,
                "onStopSuper");
        onDestroyMethod.init(bridge, null,
                "onDestroySuper");
        onActivityResultintintIntentMethod.init(bridge, null,
                "onActivityResultSuper", int.class, int.class, Intent.class);
    }

}
